package dproxy.server.net.protocol;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import dproxy.server.common.exceptions.AppInfrastructureException;
import dproxy.server.common.exceptions.ResponseReadException;
import dproxy.server.common.util.UtilConvert;
import dproxy.server.net.backoff.Timeout;

/**
 * Reader for Simple XML Over TCP-Socket protocol.
 */
public class XMLOSProtocol extends BaseProtocol {

    /** Stream buffer lenght. */
    private static final int READ_BUFFER_SIZE = 512;

    /** Logger. */
    private Log logger = LogFactory.getLog(getClass());

    /** Root element of XML message. */
    private String endElement;
    
    /** Error element of XML message. */
    private String errorElement;
    
    /**
     * @see dproxy.server.net.protocol.BaseProtocol#init()
     */
    @Override
    public void init() {
        errorElement = getInitParams().get("error-element");
        if (errorElement == null 
              || errorElement.trim().equals("")) {
            logger.warn("error-element not defined for XMLOS protocol. Using default <error-element />");
            errorElement = "error-element";
        }
        endElement = getInitParams().get("end-element");
        if (endElement == null 
                || endElement.trim().equals("")) {
              logger.warn("end-element not defined for XMLOS protocol. Using default <end-element />");
              endElement = "end-element";
        }
        
        if (logger.isDebugEnabled()) {
            logger.debug("Initiating XMLOS Protocol");
        }
    }
    
    /**
     * @see dproxy.server.net.protocol.Protocol#getName()
     */
    public String getName() {
        return "XMLOS";
    }

    /**
     * @see dproxy.server.net.protocol.Protocol#generateKey(byte[])
     */
    public String generateKey(byte[] request) {
        return createSimpleKey(request);
    }

    /**
     * @see dproxy.server.net.protocol.Protocol#readRequest(InputStream)
     */
    public byte[] readRequest(InputStream inputStream) {
        byte[] request = getXMLByteArrayFromInputStream(inputStream);
        return request;
    }

    /**
     * @see dproxy.server.net.protocol.Protocol#readResponse(InputStream)
     */
    public byte[] readResponse(InputStream inputStream)
            throws ResponseReadException {
        byte[] response = getXMLByteArrayFromInputStream(inputStream);
        
        String strResponse = UtilConvert.convertToString(response);
        int indexOfError = strResponse.indexOf(errorElement);
        if (indexOfError > 0) {
            /* not really useful...
            indexOfError += errorElement.length();
            int lastChar = indexOfError + 40;
            if (lastChar > strResponse.length() -1) {
                lastChar = strResponse.length() -1;
            }
            String errorDetails = strResponse.substring(indexOfError, lastChar);*/
            String errorDetails = "";
            throw new ResponseReadException(response, errorDetails);
        }
        
        return response;
    }

    /**
     * Get XML data from network stream.
     * @param inputStream Stream
     * @return byte array
     */
    private byte[] getXMLByteArrayFromInputStream(InputStream inputStream) {
        byte[] bs;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            while (true) {
                // Read a set of characters from the socket
                int i;
                byte[] buffer = new byte[READ_BUFFER_SIZE];
                i = inputStream.read(buffer);
                for (int j=0; j<i; j++) {
                    baos.write((byte)buffer[j]);
                }
                if (inputStream.available() == 0) {
                    // Look for </rootElement>, wich means request end
                    String streamContents = UtilConvert.convertToString(baos.toByteArray());
                    if (streamContents.indexOf(endElement) > 0) {
                        break;
                    } else {
                        Timeout.getInstance().waitForDataAvailable(inputStream);
                        if (inputStream.available() == 0) {
                            throw new AppInfrastructureException("Read timeout : No data " +
                                    "could be read from socket after " + 
                                    (Timeout.getInstance().getReadTimeout()) +
                                    " msecs");
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new AppInfrastructureException(e);
        }
        bs = baos.toByteArray();
        return bs;
    }
    
}
